package com.innovation.wjrpc.rpc;

import com.innovation.wjrpc.codec.Decoder;
import com.innovation.wjrpc.codec.Encoder;
import com.innovation.wjrpc.communication.HTTPTransportClient;
import com.innovation.wjrpc.loader.TransportSelector;
import com.innovation.wjrpc.protocol.Request;
import com.innovation.wjrpc.protocol.Response;
import com.innovation.wjrpc.protocol.ServiceDescriptor;
import com.innovation.wjrpc.result.ResultsMap;
import com.innovation.wjrpc.transport.TransportClient;
import com.innovation.wjrpc.util.MethodHandlesUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author innoyiya
 * @date 2021/9/7 19:44
 */
@Slf4j
public abstract class RemoteInvoker implements InvocationHandler {
    protected Class clazz;
    protected Encoder encoder;
    protected Decoder decoder;
    protected TransportSelector selector;

    private ConcurrentHashMap<Method, MethodHandle> methodHandleMap = new ConcurrentHashMap<>();

    public RemoteInvoker(Class clazz, Encoder encoder, Decoder decoder, TransportSelector selector) {
        this.clazz = clazz;
        this.decoder = decoder;
        this.encoder = encoder;
        this.selector = selector;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) {
        //调用default方法
        if (method.isDefault()) {
            MethodHandle defaultMethodHandle = methodHandleMap.computeIfAbsent(method, key -> {
                MethodHandle methodHandle = MethodHandlesUtil.getSpecialMethodHandle(method);
                return methodHandle.bindTo(proxy);
            });
            try {
                return defaultMethodHandle.invokeWithArguments(args);
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
        }
        Request request = new Request();
        request.setId(ResultsMap.GenerateId());
        request.setService(ServiceDescriptor.from(clazz, method));
        request.setParameters(args);

        Response response = invokeRemote(request);
        if (response == null || response.getCode() == 0) {
            throw new IllegalStateException("fail to invoke remote: " + response);
        }
        return response.getData();
    }

    abstract Response invokeRemote(Request request);
}
